#include <assert.h>

#include "mcl/parser.h"
#include "mcl/log.h"
#include "mcl/sym.h"
#include "mcl/val.h"
#include "mcl/rt.h"
#include "mcl/code.h"
#include "mcl/config.h"

struct parser new_parser(struct runtime *rt, char const *code)
{
	return (struct parser){
		.rt = rt,
		.code = code,
		.curr = 0,
		.pos = {.row = 1, .col = 1},
	};
}

static int peek(struct parser *p)
{
	return p->code[p->curr];
}

static int pop(struct parser *p)
{
	int c = peek(p);
	p->curr += 1;

	if (c == '\n') {
		p->pos.row += 1;
		p->pos.col = 1;
	} else {
		p->pos.col += 1;
	}
	return c;
}

static bool is_ws(int c)
{
	switch (c) {
	case ' ':
	case '\r':
	case '\n':
	case '\t':
		return true;
	default:
		return false;
	}
}

static int skip_ws(struct parser *p)
{
	int c = peek(p);

	while (is_ws(c) && c != 0) {
		pop(p);
		c = peek(p);
	}
	return c;
}

// 解析块。
//
// (aa { xx; xx; xx; } bb) 等效于 (aa (xx) (xx) (xx) bb)。
//
// 将解析到的列表都插入 outter_ls。
static enum code parse_block(struct parser *p, struct slice *outter_ls)
{
	int c = skip_ws(p);
	if (c != '{') {
		logerror("block not starts with '{'");
		return CODE_OOPS;
	}
	pop(p);

	struct slice ls = make_slice();

	while (true) {
		int c = skip_ws(p);

		if (c == ';') {
			struct lvalue_uninit *v = append(outter_ls);

			v->val.type = MC_LIST;
			v->val.via.list = ls;

			ls = make_slice();

			pop(p);
			continue;
		}

		if (c == '}') {
			if (ls.len != 0) {
				logerror("missing ';' before end of block");
				return CODE_OOPS;
			}

			pop(p);
			break;
		}

		if (c == 0) {
			logerror("reach EOF before block close");
			return CODE_OOPS;
		}

		struct lvalue_uninit *v = append(&ls);
		struct lvalue_opt val_opt = parse_val(p, v);

		if (val_opt.code != CODE_OK) {
			logerror("failed to parse element of list");
			return CODE_OOPS;
		}
	}

	return CODE_OK;
}

static enum code parse_list(struct parser *p, struct slice *ls)
{
	int c = skip_ws(p); // (
	if (c != '(') {
		logerror("list not starts with '('");
		return CODE_OOPS;
	}
	pop(p);

	while (true) {
		int c = skip_ws(p);

		if (c == 0) {
			logerror("reach EOF before '(' close");
			return CODE_OOPS;
		}

		if (c == ')') {
			pop(p);
			break;
		}

		if (c == '{') {
			enum code code = parse_block(p, ls);
			if (code != CODE_OK) {
				logerror("failed to parse block in list");
				return CODE_OOPS;
			}

			continue;
		}

		struct lvalue_uninit *v = append(ls);

		struct lvalue_opt val_opt = parse_val(p, v);
		if (val_opt.code != CODE_OK) {
			logerror("failed to parse element of list");
			return CODE_OOPS;
		}
	}

	return CODE_OK;
}

static bool parse_i64(struct parser *p, int64_t *x)
{
	int c = skip_ws(p);

	*x = 0;

	while (true) {
		if (('0' <= c && c <= '9')) {
			pop(p);
			(*x) = (*x) * 10 + (c - '0');
			c = peek(p);
		} else {
			break;
		}
	}

	return true;
}

static bool parse_sym(struct parser *p, struct dstr *sym)
{
	int c = skip_ws(p);

	DA_CLEAR(*sym);
	if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) {
		pop(p);
		DA_APPEND(*sym, c);
		c = peek(p);
	} else {
		return false;
	}

	while (true) {
		if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') ||
		    ('0' <= c && c <= '9') || c == '_') {
			pop(p);
			DA_APPEND(*sym, c);
			c = peek(p);
		} else {
			break;
		}
	}

	return true;
}

bool eof(struct parser *p)
{
	return skip_ws(p) == 0;
}

static struct dstr_view parse_comment(struct parser *p)
{
	int c = skip_ws(p);

	if (c != '#') {
		return (struct dstr_view){.data = NULL, .len = 0};
	}

	usize start = p->curr;
	while (c == '#') {
		while (c != '\n') {
			pop(p);
			c = peek(p);
		}

		c = skip_ws(p);
	}

	usize len = p->curr - start;

	return (struct dstr_view){.data = p->code + start, .len = len};
}

#if IS_ON(CONFIG_TEST)
void test_parse_comment(void)
{
	struct parser p = {
		.code = "   ; this is comment \n  ; this is comment \n()",
		.curr = 0,
		.pos = {.row = 1, .col = 1},
	};

	struct dstr_view cmt = parse_comment(&p);

	char const *expect = "; this is comment \n  ; this is comment \n";
	CHECK(strncmp(cmt.data, expect, cmt.len) == 0);
}
#endif

struct lvalue_opt parse_val(struct parser *p, struct lvalue_uninit *val_raw)
{
	enum code return_code;
	struct lvalue *return_val;

#define return_(CODE, V) \
	return_code = (CODE); \
	return_val = (V); \
	goto finish

	int c = skip_ws(p);
	if (c == 0) {
		return_(CODE_OOPS, NULL);
	}

	if (c == '#') {
		struct dstr_view comment = parse_comment(p);

		struct lvalue_opt val_opt = parse_val(p, val_raw);
		if (val_opt.code != CODE_OK) {
			logerror("missing value after comment");
			return_(CODE_OOPS, NULL);
		}

		val_opt.val->comment = comment;

		return_(CODE_OK, val_opt.val);
	}

	if (c == '\'') {
		pop(p);

		struct lvalue *result = init_val_list(val_raw);

		struct lvalue_uninit *quote = append(&result->via.list);
		init_val_sym(quote, SYM.quote);

		struct lvalue_uninit *quoted_val = append(&result->via.list);
		struct lvalue_opt val_opt = parse_val(p, quoted_val);
		if (val_opt.code != CODE_OK) {
			logerror("missing value after comment");
			return_(val_opt.code, NULL);
		}

		return_(CODE_OK, result);
	}

	if (c == '(') {
		struct lvalue *result = init_val_list(val_raw);
		enum code code = parse_list(p, &result->via.list);

		if (code != CODE_OK) {
			logerror("failed to parse list");
			return_(code, NULL);
		}

		return_(CODE_OK, result);
	}

	if (c == '+' || c == '-' || c == '*' || c == '/') {
		pop(p);

		struct dstr op = DA_INIT;
		DA_APPEND(op, c);
		struct lvalue *result = init_val_sym(val_raw, op);

		return_(CODE_OK, result);
	}

	if ('a' <= c && c <= 'z') {
		struct dstr sym = DA_INIT;

		if (!parse_sym(p, &sym)) {
			return_(CODE_OOPS, NULL);
		}

		if (dstr_eq_cstr(&sym, "nil")) {
			DA_DELETE(sym);
			struct lvalue *result = init_val_nil(val_raw);

			return_(CODE_OK, result);
		}

		if (dstr_eq_cstr(&sym, "true")) {
			DA_DELETE(sym);
			struct lvalue *result = init_val_bool(val_raw, true);

			return_(CODE_OK, result);
		}

		if (dstr_eq_cstr(&sym, "false")) {
			DA_DELETE(sym);
			struct lvalue *result = init_val_bool(val_raw, false);

			return_(CODE_OK, result);
		}

		struct lvalue *result = init_val_sym(val_raw, sym);

		return_(CODE_OK, result);
	}

	if ('0' <= c && c <= '9') {
		int64_t x;
		if (!parse_i64(p, &x)) {
			return_(CODE_OOPS, NULL);
		}

		struct lvalue *result = init_val_i64(val_raw, x);

		return_(CODE_OK, result);
	}

	logerror("invalid leading char: `%c' (%d)", c, c);
	return_(CODE_OOPS, NULL);

finish:
	if (return_val == NULL) {
		return_val->pos = p->pos;
	}

	return (struct lvalue_opt){.code = return_code, .val = return_val};
}

#if IS_ON(CONFIG_TEST)
void test_parse(void)
{
	struct runtime rt = RUNTIME_INIT;
	struct parser p = new_parser(&rt, " a01 bc () (110) (a b c) (+ 10 2)");

	struct dstr sym = DA_INIT;

	// parse a01
	CHECK(parse_sym(&p, &sym));
	CHECK(dstr_eq_cstr(&sym, "a01"));

	// parse bc
	CHECK(parse_sym(&p, &sym));
	CHECK(dstr_eq_cstr(&sym, "bc"));

	struct slice ls = make_slice();
	enum code code = parse_list(&p, &ls);

	CHECK(code == CODE_OK);

	// parse ()
	CHECK(ls.len == 0);

	// parse (110)
	code = parse_list(&p, &ls);
	CHECK(ls.len == 1);

	val_dbg("ls[0]", &ls.data[0]);

	CHECK(ls.data[0].type == MC_I64);
	CHECK(ls.data[0].via.i64 == 110);

	struct val_uninit val_raw;
	struct val_opt val_opt = parse_val(&p, &val_raw);
	struct val *val = val_opt.val;

	CHECK(val_opt.code == CODE_OK);

	// parse (a b c)
	CHECK(val->type == MC_LIST);

	{
		struct val *data = val->via.list.data;

		CHECK(data[0].type == MC_SYM);
		CHECK(dstr_eq_cstr(&data[0].via.sym, "a"));

		CHECK(data[1].type == MC_SYM);
		CHECK(dstr_eq_cstr(&data[1].via.sym, "b"));

		CHECK(data[2].type == MC_SYM);
		CHECK(dstr_eq_cstr(&data[2].via.sym, "c"));
	}
}
#endif
